Skip to main content

14 消息队列:V8实现回调函数

什么是回调函数

回调函数也是个函数,具有函数的所有特征,它可以有参数和返回值。当某个函数被作为参数,传递给另外一个函数,或者传递给宿主环境,然后该函数在函数内部或者在宿主环境中被调用,则被称为回调函数。

回调函数有两种不同的形式,同步回调和异步回调。同步回调函数在执行函数内部被执行,而异步回调函数在执行函数外部被执行。

var myArray = ["water", "goods", "123", "like"];
function handlerArray(indexName, index) {
console.log(index + 1 + ". " + indexName);
}
myArray.forEach(handlerArray);

handlerArrayforEach 的参数,而且 handlerArray 是在 forEach 函数内部执行,所以这是一个同步回调。

异步回调函数并不是在它的执行函数内部被执行的,而是在其他的位置和其他的时间点被执行。

function foo() {
alert("Hello");
}
setTimeout(foo, 3000);

V8 执行 setTimeout 时,会立即返回,等待 3000 毫秒之后,foo 函数才会被 V8 调用,foo 函数并不是在 setTimeout 函数内部被执行的,所以是一个异步回调。

UI 线程的宏观架构

早期浏览器的页面是运行在一个单独的 UI 线程中的,所以要在页面中引入 JavaScript,那么 JavaScript 也必须要运行在和页面相同的线程上,这样才能方便使用 JavaScript 来操纵 DOM。

在大部分情况下,UI 线程并不能立即响应和处理页面所触发的事件(移动鼠标的过程中,每移动一个像素都会产生一个事件,鼠标移动的事件会频繁地被触发)。页面线程可能正在处理前一个事件,那么最新的事件就无法被立即执行。

为 UI 线程提供一个消息队列,并将这些待执行的事件添加到消息队列中,UI 线程会不断循环地从消息队列中取出事件、执行事件。UI 线程每次从消息队列中取出事件,执行事件的过程称为一个任务。

function UIMainThread() {
while (queue.waitForMessage()) {
Task task = queue.getNext()
processNextMessage(task)
}
}

异步回调函数的调用时机

页面主线程执行任务的过程中调用 setTimeout(foo, 3000),宿主会将 foo 函数封装成一个事件,并添加到消息队列中,setTimeout 函数执行结束。主线程继续不间断地从消息队列中取出新的任务,执行新的任务,等到时机合适,便取出 setTimeout 设置的 foo 函数的回调的任务,然后就可以直接执行 foo 函数的调用了。

XMLHttpRequest 是用来下载网络资源的,但是实际的下载过程却并不适合在主线程上执行,因为下载任务会消耗比较久的时间,如果在 UI 线程上执行,那么会阻塞 UI 线程,这就会拖慢 UI 界面的交互和绘制的效果。所以当主线程从消息队列中取出来了这类下载任务之后,会将其分配给网络线程,让其在网络线程上执行下载过程,这样就不会影响到主线程的执行了。